#pragma once
#include <cstdint>
#include <functional>

using MurMur2_t = uint32_t;
#define STRINGTOKEN_MURMURHASH_SEED 0x31415926

namespace MURMUR2
{
    inline MurMur2_t Hash(const char* str, MurMur2_t seed)
    {
        if (!str) return 0;

        const uint32_t m = 0x5bd1e995;
        const int r = 24;
        uint32_t len = 0;

        while (str[len]) len++;

        uint32_t h = seed ^ len;
        const unsigned char* data = (const unsigned char*)str;

        while (len >= 4)
        {
            uint32_t k = *(uint32_t*)data;
            k *= m;
            k ^= k >> r;
            k *= m;
            h *= m;
            h ^= k;
            data += 4;
            len -= 4;
        }

        switch (len)
        {
        case 3: h ^= data[2] << 16;
        case 2: h ^= data[1] << 8;
        case 1: h ^= data[0];
            h *= m;
        };

        h ^= h >> 13;
        h *= m;
        h ^= h >> 15;

        return h;
    }

    constexpr size_t StrLen(const char* str)
    {
        return *str ? 1 + StrLen(str + 1) : 0;
    }

    constexpr MurMur2_t HashConst(const char* str, MurMur2_t seed, size_t len = 0)
    {
        const uint32_t m = 0x5bd1e995;
        const int r = 24;
        size_t actualLen = len ? len : StrLen(str);
        uint32_t h = seed ^ (uint32_t)actualLen;
        const unsigned char* data = (const unsigned char*)str;

        if (actualLen >= 4)
        {
            uint32_t k = *(const uint32_t*)data;
            k *= m;
            k ^= k >> r;
            k *= m;
            h *= m;
            h ^= k;
            return HashConst((const char*)(data + 4), h, actualLen - 4);
        }

        switch (actualLen)
        {
        case 3: h ^= data[2] << 16;
        case 2: h ^= data[1] << 8;
        case 1: h ^= data[0];
            h *= m;
        }

        h ^= h >> 13;
        h *= m;
        h ^= h >> 15;

        return h;
    }

    inline uint32_t MurmurHash2(const void* key, int len, uint32_t seed)
    {
        const uint32_t m = 0x5bd1e995;
        const int r = 24;

        uint32_t h = seed ^ len;
        const unsigned char* data = (const unsigned char*)key;

        while (len >= 4)
        {
            uint32_t k = *(uint32_t*)data;

            k *= m;
            k ^= k >> r;
            k *= m;

            h *= m;
            h ^= k;

            data += 4;
            len -= 4;
        }

        switch (len)
        {
        case 3: h ^= data[2] << 16;
        case 2: h ^= data[1] << 8;
        case 1: h ^= data[0];
            h *= m;
        };

        h ^= h >> 13;
        h *= m;
        h ^= h >> 15;

        return h;
    }
}